########################################################################
# fncheck.py - Mechanism for checking file names against a set of
#	match rules.  Match rules can be literal strings, regexes, or both.
#
# Created 2006.09.10 by Warren Young
#
# Copyright (c) 2006 by Pixel Corps.  This program may be used under
# the terms of the X11 license, a copy of which should have accompanied
# this program, in the LICENSE file.
########################################################################

import localparms

import os.path, sre


# max # of REs is 8
ordinals = ('first', 'second', 'third', 'fourth', 'fifth', 'sixth',
		'seventh', 'eighth')
literals = [''] * len(localparms.parms.fileNameCheckREs)


class Tokenizer:
	"""
	Breaks a file name up into tokens.  Strips off the extension,
	then breaks the rest up according to the application's file
	name separator parameter.  File name is expected to be a
	basename only.
	"""

	separator = localparms.parms['FileNameCheckSeparator']
	
	def __init__(self, filename):
		"""
		Tokenize the given file name using the file name check separator
		parameter.

		@type filename: string
		@param filename: file name to tokenize
		"""
	
		root, ext = os.path.splitext(filename)
		if root:
			self.tokens_ = root.split(self.separator)
		else:
			self.tokens_ = []

	def __iter__(self):
		self.index_ = -1
		return self

	def __getitem__(self, index):
		return self.tokens_[index]

	def __len__(self):
		return len(self.tokens_)

	def __str__(self):
		return '(%s)' % ', '.join(self.tokens_)

	def next(self):
		self.index_ += 1
		if self.index_ < len(self):
			return self.tokens_[self.index_]
		else:
			raise StopIteration


def CheckLiteral(lit, tok, rule, ord):
	"""
	Check a given file name token against a literal value. Depending on
	the rule type, we take different actions if there is a mismatch.

	@type lit: string
	@param lit: the literal string to match the token against

	@type tok: string
	@param tok: the token to match against the literal string

	@type rule: string
	@param rule: the rule governing how we deal with lit != tok. See the
	documentation for the parameters file for information on matching
	rules.

	@type ord: string
	@param ord: ordinal string (e.g. "second" for the 2nd token) passed
	in just to make the debug log messages clearer.

	@rtype: boolean
	@return: True if the literal matches the token or the nature of the
	mismatch isn't severe enough to abort the upload (i.e. "I'll let
	you off with a warning, Mister.")
	"""

	if lit == tok:
		print "FNCHECK: %s literal '%s' == '%s'" % (ord, lit, tok)
		return True
	elif rule == 'warn':
		print "FNCHECK: %s part of file name '%s' should be '%s'." % (
				ord, tok, lit)
		return True
	elif rule == 'ask':
		print "FNCHECK: assuming user OK'd literal mismatch: '%s' != '%s'" % (
				tok, lit)
		return True
	elif rule == 'rename':
		print "FNCHECK: %s literal '%s' should be '%s'!" % (ord, lit, tok)
		return False
	else:
		print "FNCHECK: unknown literal rule check type '%s'!" % rule
		return True


def CheckFileName(filename, regexes):
	"""
	Feeds passed file name to the tokenizer, then checks the
	resulting tokens against the passed-in regex list and the
	global string literal list.  The lengths of the token, regex
	and string literal lists must all match.

	@type filename: string
	@param filename: name of file to tokenize

	@type regexes: array of RE pattern objects
	@param regexes: regular expressions used in checking file name tokens

	@rtype: string
	@return: error message for the first mismatch found, or None if
	all checks pass.
	"""

	print 'FNCHECK: literals:', literals
	if len(literals) != len(regexes):
		return "Match lists are not the same length! Check parameter file."

	basename = os.path.basename(filename)

	(root, ext) = os.path.splitext(basename)
	if ext == '.xml':
		print 'FNCHECK: ignoring likely slate file', basename
		return None
	
	tokens = Tokenizer(basename)
	if len(tokens) == len(regexes):
		for tok, re, ord, lit, rule in zip(
				list(tokens), regexes, ordinals, literals,
				localparms.parms.fileNameCheckLiteralRules):
			if not sre.match(re, tok):
				return "'%s' doesn't match %s file name rule" % (
						tok, ord)
			elif lit and not CheckLiteral(lit, tok, rule, ord):
				return "'%s' should be '%s' in %s part of file name" % (
						tok, lit, ord)
			else:
				print "FNCHECK: token '%s' matches %s rule set" % (
						tok, ord)

		print 'FNCHECK: %s passes all checks!' % basename
		return None
	else:
		if len(tokens) > len(regexes): errType = 'Too many'
		else: errType = 'Not enough'

		return "%s '%s' separated parts; found %d, expected %d." % (
				errType, tokens.separator, len(tokens), len(regexes))


def CheckFileNames(files):
	"""
	Compiles the regexes in the parameter file to an array of
	REs, then passes it along with each file name in turn to
	CheckFileName().

	@type files: array of string
	@param files: list of fine names to check against the match rules
	
	@rtype: array of string
	@return: CheckFileName()'s results for each given file
	"""

	regexes = []
	for r in localparms.parms.fileNameCheckREs:
		print 'FNCHECK: compiling RE', r
		regexes.append(sre.compile('%s$' % r))

	errors = []
	for f in files:
		error = CheckFileName(f, regexes)
		if error:
			print 'FNCHECK ERROR:', error
		errors.append(error)

	return errors

